package time.runtime;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;

private typedef TType = haxe.macro.Type;
#if !display
import haxe.macro.MacroTools;
using Lambda;
#end
#end
/**
 * 
 * @author waneck
 */

class Config 
{
	@:alias("Default")
	public static var provider:time.providers.Provider;
	
	@:macro public static function init(e:Expr):Expr
	{
		#if display
		return e;
		#else
		var vars:Hash<Expr> = new Hash();
		var block:Array<Expr>;
		var addedExprs:Array<Expr>;
		var foundProvider = false;
		
		//algo: sempre ficar no nível dos TBlocks
		//procurar exprs EBinop. Verificar o lado esq se é uma path
		//se for, adicionar tbm Provider.setProvider("path", val, ["meta", "options"], "nickname");
		function traverse(e:Expr)
		{
			if (null == e)
				return null;
			
			return switch(e.expr)
			{
				case EBinop(op, e1, e2):
					switch(op)
					{
						case OpAssign:
							switch(e1.expr)
							{
								case EType(f, s), EField(f, s):
									var path = MacroTools.separatePackage(MacroTools.getPath(f));
									
									var p = path.pack;
									p.push(path.type);
									
									var t = Context.getType(p.join("."));
									if (null == t) //invalid type
									{
										var tof = Context.typeof(f);
										switch(tof)
										{
											case TType(td,_):
												var path = td.toString();
												if (path.substr(0,1) == "#")
												{
													t = Context.getType(path.substr(1));
												}
											default:
										}
									}
									
									if (null == t)
									{
										Context.error("Invalid path location. Are you sure it is correct?", f.pos);
									}
									
									var path = switch(t)
									{
										case TInst(i,_): i.toString();
										case TType(td,_): td.toString();
										default: Context.error("Invalid type. Class type or typedef expected.", f.pos);
									};
									
									var metas = [];
									var meta = MacroTools.mk(EArrayDecl(metas), e.pos);
									var alias = MacroTools.mk(EConst(CIdent("null")), e.pos);
									
									switch(Context.follow(t))
									{
										case TInst(i,_):
											var i = i.get();
											
											var found = false;
											for (field in i.statics.get())
											{
												if (field.name == s)
												{
													found = true;
													for (m in field.meta.get())
													{
														
														switch(m.name)
														{
															case "alias", ":alias": 
																alias = m.params[0];
																if (null == alias)
																	Context.error("Alias metadata must have one parameter", m.pos);
															default: 
																var n = m.name;
																if (n.charCodeAt(0) == ":".code) n = n.substr(1);
																metas.push(MacroTools.mk(EConst(CString(n)), e.pos));
														}
													}
												}
											}
											if (!found) Context.error("The Provider Placeholder variable must be a static variable", e.pos);
										default: Context.error("The Provider Placeholder variable must be a static variable", e.pos);
									}
									
									addedExprs.push(MacroTools.mk(ECall(
										MacroTools.path(["time", "providers", "Provider", "set"], e.pos),
										[
										 MacroTools.mk(EConst(CString(path + "." + s)), e.pos), 
										 e1,
										 meta,
										 alias
										]
									), e.pos));
									
									MacroTools.mkc(EBinop(op, e1, traverse(e2)), e);
								default: e;
							}
						default: e;
					}
				case ENew( t, params ):
					var tof = Context.typeof(e);
					switch(tof)
					{
						case TInst(i, _):
							var i = i.get();
							for (meta in i.meta.get())
							{
								if (meta.name == ":generatorClass")
								{
									var generatorClass:String = MacroTools.getString(meta.params[0]);
									
									var pack = generatorClass.split(".");
									var name = pack.pop();
									addedExprs.push(
									MacroTools.mk(EVars( [{name : "___temp___", type: TPath({ pack: pack, name: name, params: [], sub: null }), expr:null} ]), e.pos
										)
									);
									
									break;
								}
							}
						default:
					}
					
					MacroTools.mk(ENew(t, params.map(traverse).array()), e.pos);
				case EBlock(b):
					var oldadded = addedExprs;
					var oldbl = block;
					var b2 = block = [];
					addedExprs = [];
					
					for (e in b)
						b2.push(traverse(e));
					
					for (added in addedExprs)
						b2.push(added);
					
					addedExprs = oldadded;
					block = oldbl;
					MacroTools.mkc(EBlock(b2), e);
				case EFor(v, it, expr):
					MacroTools.mk(EFor(v, it, traverse(expr)), e.pos);
				case EIf(econd, eif, eelse):
					MacroTools.mk(EIf(econd, traverse( (eif)), traverse( (eelse))), e.pos);
				case EWhile(econd, e, normalWhile):
					MacroTools.mk(EWhile(econd, traverse( (e)), normalWhile), e.pos);
				case ESwitch(e, cases, edef):
					MacroTools.mk(ESwitch(e, cases.map(function(c) return {values:c.values.map(function(e) return e).array() , expr:traverse( (c.expr)) } ).array(), traverse( (edef))), e.pos);
				case ETry(e, catches):
					MacroTools.mk(ETry(traverse( (e)), catches.map(function(c) return {name:c.name, type:c.type, expr:traverse( (c.expr))}).array()), e.pos);
				default: e;
			}
		}
		
		var a = traverse(e);
		trace(a);
		return a;
		#end
	}
}